#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include "debug.h"
#include "os_api.h"
#include "ps_api.h"
#include "dev_api.h"
#include "mqttclient.h"
#include "os_api.h"
#include "debug.h"
#include "hal/gpio.h"
#include "lpconfig.h"
#include "sockets.h"
#include "def.h"
#include "netdb.h"

typedef struct tcp_msg_s {
    uint32_t msgId;
    uint32_t extra;
    void *data;
} tcp_msg_t;

typedef struct tcp_data_s {
    OSMsgQRef cmdq;
    OSTaskRef task_ref;
    void *task_stack;

    void *task_net_stack;
    OSTaskRef task_net_ref;
    OSAFlagRef flag_net_ref;

    OSMutexRef mutex_ref;

    UINT32 task_timeout;

    int fd_sock;

    int heart_miss_count;

} tcp_data_t;

static tcp_data_t *tcp_ad = NULL;

#define CONNECT_RETRY_MAX            3
#define SEND_RETRY_MAX            3


int tcp_send_msg(uint32_t msgId, void *data)
{
    tcp_data_t *ad = tcp_ad;
    tcp_msg_t msg;

    if (ad && ad->cmdq) {
        memset(&msg, 0, sizeof(msg));
        msg.msgId = msgId;
        msg.data = data;
        if (OS_SUCCESS == OSAMsgQSend(ad->cmdq, sizeof(msg), (UINT8 *)&msg, OSA_NO_SUSPEND)) {
            return 0;
        }
    }
    return -1;
}


void tcp_wakeup(void)
{
    lp_wakeup();
}

void tcp_suspend(void)
{
    lp_sleep();
}

static void tcp_net_close(tcp_data_t *ad)
{
    int fd, closing = 0;

    LOG_PRINTF("%s: +\n", __func__);
    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    if (ad->fd_sock >= 0) {
        fd = ad->fd_sock;
        LOG_PRINTF("%s: try close connection\n", __func__);
        ad->fd_sock = -1;
        close(fd);

        closing = 1;
    }
    OSAMutexUnlock(ad->mutex_ref);
    if (closing)
        OSATaskYield();
    LOG_PRINTF("%s: -\n", __func__);
}

static int tcp_net_send(tcp_data_t *ad, const void *data, size_t sz)
{
    const char *d = (const char *)data;
    size_t cur = 0;
    int ret;
    while (cur < sz) {
        ret = send(ad->fd_sock, d + cur, sz - cur, 0);
        if (ret < 0) {
            return -1;
        } else if (!ret) {
            return -1;
        }
        cur += ret;
    }
    return 0;
}

static int tcp_net_init(tcp_data_t *ad)
{
    int ret;
    struct timeval tv;
	fd_set rfds;
    lpSetCfg *cfg = getLpCfgInfo();

    char buf[512] = {0};

    // 如果设置login包，tcp连接成功后，立即发送login包，
    if (strlen(cfg->tcp_login) == 0) {
        ret = tcp_net_send(ad, cfg->tcp_heart, strlen(cfg->tcp_heart));
        if (ret < 0) {
            ret = -1;
            goto clean;
        }
    } else {        
        ret = tcp_net_send(ad, cfg->tcp_login, strlen(cfg->tcp_login));
        if (ret < 0) {
            ret = -1;
            goto clean;
        }
        
        // 如果设置login_resp包，标识需要校验服务器下发的login_resp，校验成功即init_ok
        if (strlen(cfg->tcp_login_resp) > 0) 
        {        
            FD_ZERO(&rfds);
            FD_SET(ad->fd_sock, &rfds);
            tv.tv_sec = 10;
            tv.tv_usec = 0;
            ret = select(ad->fd_sock + 1, &rfds, NULL, NULL, &tv);
            if (ret != 1 || !FD_ISSET(ad->fd_sock, &rfds)) {
                ret = -1;
                goto clean;
            }
            ret = recv(ad->fd_sock, buf, sizeof(buf)-1, 0);
            if (ret <= 0) {
                ret = -1;
                goto clean;
            }
            buf[ret] = '\0';

            if (ret >= strlen(cfg->tcp_login_resp) && !memcmp(buf, cfg->tcp_login_resp, strlen(cfg->tcp_login_resp))) {
                goto init_ok;
            }
            ret = -1;	
            goto clean;
        }
    }

init_ok:
	
    ret = 0;
clean:
    return ret;
}

static int tcp_net_connect(tcp_data_t *ad, int fd, const struct sockaddr *addr, socklen_t addrlen)
{
    int ret;
    struct timeval tv;
    int sockflag, optval;
    socklen_t optlen;
    struct linger lin;
	fd_set wfds;

    sockflag = fcntl(fd, F_GETFL, 0);
    if (sockflag == -1) {
        ret = -1;
        goto clean;
    }
    ret = fcntl(fd, F_SETFL, sockflag | O_NONBLOCK);
    if (ret) {
        ret = -1;
        goto clean;
    }

    ret = connect(fd, addr, addrlen);
    if (!ret) {
        goto connect_ok;
    }
    if (lwip_getsockerrno(fd) != EINPROGRESS) {
        ret = -1;
        goto clean;
    }

    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);
    tv.tv_sec = 10;
    tv.tv_usec = 0;
    ret = select(fd+1, NULL, &wfds, NULL, &tv);
    if (ret != 1 || !FD_ISSET(fd, &wfds)) {
        ret = -1;
        goto clean;
    }
    optlen = sizeof(optval);
    ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, (socklen_t *)&optlen);
    if (ret || optval) {
        goto clean;
    }

connect_ok:
    sockflag = fcntl(fd, F_GETFL, 0);
    if (sockflag == -1) {
        ret = -1;
        goto clean;
    }
    ret = fcntl(fd, F_SETFL, sockflag & (~O_NONBLOCK));
    if (ret) {
        ret = -1;
        goto clean;
    }

    optval = 1;
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
    lin.l_onoff = 1;
    lin.l_linger = 0;
    setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));

    return 0;
clean:
    return ret;
}

static int tcp_net_create(tcp_data_t *ad)
{
    struct sockaddr_in addr;
    struct sockaddr_in6 addr6;
    struct addrinfo hints, *result = NULL, *rp;
    struct timeval tv;
    int fd = -1, ret = -1;
    char buf[64];

    lpSetCfg *cfg = getLpCfgInfo();

    if (!cfg->hostname[0] || !cfg->port) {
        ret = -1;
        goto clean;
    }	

    memset(&addr, 0, sizeof(addr));
    memset(&addr6, 0, sizeof(addr6));
    if (inet_pton(AF_INET, cfg->hostname, &addr.sin_addr) == 1) {
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
            ret = -1;
            goto clean;
        }
        addr.sin_family = AF_INET;
        addr.sin_port = htons(cfg->port);
        ret = tcp_net_connect(ad, fd, (struct sockaddr *)&addr, sizeof(addr));
        if (ret < 0) {
            goto clean;
        }
    } else if (inet_pton(AF_INET6, cfg->hostname, &addr6.sin6_addr) == 1) {
        fd = socket(AF_INET6, SOCK_STREAM, 0);
        if (fd < 0) {
            ret = -1;
            goto clean;
        }
        addr6.sin6_len = sizeof(addr6);
        addr6.sin6_family = AF_INET6;
        addr6.sin6_port = htons(cfg->port);
        ret = tcp_net_connect(ad, fd, (struct sockaddr *)&addr6, sizeof(addr6));
        if (ret < 0) {
            goto clean;
        }
    } else {
        snprintf(buf, sizeof(buf), "%u", cfg->port);

        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = 0;
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = 0;
        ret = getaddrinfo( cfg->hostname, buf, &hints, &result);
        if (ret) {
            ret = -1;
            goto clean;
        }
        for (rp = result; rp; rp = rp->ai_next) {
            fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
            if (fd < 0) {
                continue;
            }
            ret = tcp_net_connect(ad, fd, rp->ai_addr, rp->ai_addrlen);
            if (ret < 0) {
                close(fd);
                fd = -1;
                continue;
            }
            break;
        }
        freeaddrinfo(result);
        result = NULL;

        if (fd < 0) {
            ret = -1;
            goto clean;
        }
    }

    ad->fd_sock = fd;
    ret = 0;	

clean:
    if (result)
        freeaddrinfo(result);
    if (ret < 0 && fd >= 0)
        close(fd);

    return ret;
}

static int tcp_net_recv(tcp_data_t *ad)
{
    int fd, ret, closing = 0;
    uint16_t sz;
    struct timeval tv;
    fd_set rfds;
    char buf[512] = {0};

    lpSetCfg *cfg = getLpCfgInfo();

    LOG_PRINTF("%s: +\n", __func__);

    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    if (ad->fd_sock < 0) {
        LOG_PRINTF("%s: sock closed\n", __func__);
        ret = -1;
        closing = 1;
        goto net_err;
    }
    fd = ad->fd_sock;
    OSAMutexUnlock(ad->mutex_ref);

    LOG_PRINTF("%s: WAIT\n", __func__);
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    ret = select(fd + 1, &rfds, NULL, NULL, NULL);
    if (ret <= 0 || !FD_ISSET(fd, &rfds)) {
        LOG_PRINTF("%s: select error: %d\n", __func__, ret);
        OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
        ret = -1;
        if (ad->fd_sock < 0) {
            LOG_PRINTF("%s: sock is closing when waiting\n", __func__);
            closing = 1;
        }
        goto net_err;
    }
    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    if (ad->fd_sock < 0) {
        LOG_PRINTF("%s: sock is closing when waiting\n", __func__);
        ret = -1;
        closing = 1;
        goto net_err;
    }
    ret = ioctlsocket(ad->fd_sock, FIONREAD, &sz);
    if (ret < 0 || !sz) {
        LOG_PRINTF("%s: data is empty\n", __func__);
        ret = -1;
        goto net_err;
    }
    ret = recv(ad->fd_sock, buf, sizeof(buf)-1, 0);
    if (ret <= 0) {
        LOG_PRINTF("%s: recv error: %d\n", __func__, ret);
        ret = -1;
        goto net_err;
    }
    buf[ret] = '\0';
    LOG_PRINTF("%s: recv DATA: %d %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X\n", __func__, ret,
        buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
        buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);

    // 如果未设备唤醒包，任意数据（心跳相应包除外），都会被作为唤醒包
    if ((strlen(cfg->tcp_wakeup) == 0 && ret > 0)
		&& !(strlen(cfg->tcp_heart_resp)  && ret >= strlen(cfg->tcp_heart_resp) && !memcmp(buf, cfg->tcp_heart_resp, strlen(cfg->tcp_heart_resp)))) {
        LOG_PRINTF("%s: anydata WAKE\n", __func__);
        ret = 0;
        goto net_wake;
    } else if (strlen(cfg->tcp_wakeup) && ret >= strlen(cfg->tcp_wakeup) && !memcmp(buf, cfg->tcp_wakeup, strlen(cfg->tcp_wakeup))) {
        LOG_PRINTF("%s: recv WAKE\n", __func__);
        ret = 0;
        goto net_wake;
    } else if (strlen(cfg->tcp_heart_resp)  && ret >= strlen(cfg->tcp_heart_resp) && !memcmp(buf, cfg->tcp_heart_resp, strlen(cfg->tcp_heart_resp))) {
        LOG_PRINTF("%s: recv heart response\n", __func__);
        ret = 0;		
		ad->heart_miss_count = 0;
        goto clean;
    } else {
        LOG_PRINTF("%s: recv error!\n", __func__);
        ret = 0;
    }

    goto clean;

net_wake:
	
    tcp_send_msg(TCP_CMD_WAKEUP, NULL);
    goto clean;

net_err:
    if (ad->fd_sock >= 0) {
        close(ad->fd_sock);
        ad->fd_sock = -1;
    }
    if (!closing) {
        ad->task_timeout = OSA_SUSPEND;
        tcp_send_msg(TCP_CMD_STOP, NULL);
    }
clean:
    OSAMutexUnlock(ad->mutex_ref);
    LOG_PRINTF("%s: -\n", __func__);
    return ret;
}

static void tcp_timeout(tcp_data_t *ad)
{
    int ret, i;

    lpSetCfg *cfg = getLpCfgInfo();
    LOG_PRINTF("%s: +\n", __func__);
    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    if (ad->fd_sock < 0) {
        LOG_PRINTF("%s: sock closed\n", __func__);
        ad->task_timeout = OSA_SUSPEND;
        goto clean;
    }
	
    // 多次未收到心跳相应包唤醒
	if (cfg->tcp_heart_miss_max != 0 && ad->heart_miss_count >= cfg->tcp_heart_miss_max){
        LOG_PRINTF("%s: heart_miss_count >= heart_miss_max_count\n", __func__);
        ad->task_timeout = OSA_SUSPEND;
        tcp_send_msg(TCP_CMD_STOP, NULL);
        goto clean;
	}

    for (i = 0; i < SEND_RETRY_MAX; i++) {
        ret = tcp_net_send(ad, cfg->tcp_heart, strlen(cfg->tcp_heart));
        if (ret < 0) {
            OSATaskSleep(5*20); // 0.5s
            continue;
        }
        LOG_PRINTF("%s: heart OK\n", __func__);
        break;
    }

    if (i >= SEND_RETRY_MAX) {
        LOG_PRINTF("%s: heart ERROR\n", __func__);
        ad->task_timeout = OSA_SUSPEND;
        tcp_send_msg(TCP_CMD_STOP, NULL);
        goto clean;
    }
	ad->heart_miss_count ++;
    ad->task_timeout = cfg->tcp_heart_interval * 200;

clean:
    OSAMutexUnlock(ad->mutex_ref);
    LOG_PRINTF("%s: -\n", __func__);
}

static void tcp_cmd_start(tcp_data_t *ad)
{
    int ret, i, fd;

    lpSetCfg *cfg = getLpCfgInfo();

    LOG_PRINTF("%s: +\n", __func__);

    LOG_PRINTF("%s: server %s:%d\n", __func__, cfg->hostname, cfg->port);
    LOG_PRINTF("%s: heart_interval %d\n", __func__, cfg->tcp_heart_interval);
    LOG_PRINTF("%s: heart and heart_resp %s, %s\n", __func__, cfg->tcp_heart, cfg->tcp_heart_resp);
    LOG_PRINTF("%s: wakeup %s\n", __func__, cfg->tcp_wakeup);

    tcp_net_close(ad);

    // start的时候，进入休眠允许状态，如果连接失败或者其他失败会再次tcp_wakup
	tcp_suspend();

    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);

    for (i = 0; i < CONNECT_RETRY_MAX; i++) {
        ret = tcp_net_create(ad);
        if (ret < 0) {
            OSATaskSleep(5*20); // 0.5s
            continue;
        }
        break;
    }
    if (i >= CONNECT_RETRY_MAX) {
        goto init_err;
    }
    LOG_PRINTF("%s: connect OK, try init\n", __func__);

    ret = tcp_net_init(ad);
    if (ret < 0) {
        goto init_err;
    }
    LOG_PRINTF("%s: init OK\n", __func__);

    OSAFlagSet(ad->flag_net_ref, 1, OSA_FLAG_OR);

    ad->task_timeout = cfg->tcp_heart_interval * 200;
	
    tcp_send_msg(TCP_CMD_SUSPEND, NULL);

    goto clean;

init_err:
    tcp_send_msg(TCP_CMD_STOP, NULL);
    goto clean;

clean:
    OSAMutexUnlock(ad->mutex_ref);
    LOG_PRINTF("%s: -\n", __func__);
}

static void tcp_cmd_stop(tcp_data_t *ad, int extra)
{
    int fd;
    LOG_PRINTF("%s: +\n", __func__);
    tcp_net_close(ad);
    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    ad->task_timeout = OSA_SUSPEND;
    OSAMutexUnlock(ad->mutex_ref);
    tcp_send_msg(TCP_CMD_WAKEUP, NULL);
    LOG_PRINTF("%s: -\n", __func__);
}

static void tcp_cmd_suspend(tcp_data_t *ad, int extra)
{
    LOG_PRINTF("%s: +\n", __func__);
    lpSetCfg *cfg = getLpCfgInfo();
    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    ad->task_timeout = cfg->tcp_heart_interval*200;
    tcp_suspend();
    OSAMutexUnlock(ad->mutex_ref);
    LOG_PRINTF("%s: -\n", __func__);
}

static void tcp_cmd_wakeup(tcp_data_t *ad, int extra)
{
    LOG_PRINTF("%s: +\n", __func__);
    OSAMutexLock(ad->mutex_ref, OSA_SUSPEND);
    tcp_wakeup();
    OSAMutexUnlock(ad->mutex_ref);
    LOG_PRINTF("%s: -\n", __func__);
}

static void tcp_net_task(void *param)
{
    int ret;
    UINT32 flags;
    tcp_data_t *ad = param;

    while (1) {
        ret = OSAFlagWait(ad->flag_net_ref, 0xffffffff, OSA_FLAG_OR_CLEAR, &flags, OSA_SUSPEND);
        ASSERT(ret == OS_SUCCESS);
        do {
            ret = tcp_net_recv(ad);
            OSATaskSleep(1);
        } while (!ret);
    }
}

static void tcp_task(void *param)
{
    int ret;
    tcp_msg_t msg;
    tcp_data_t *ad = param;

    while (1) {
        memset(&msg, 0, sizeof(msg));
        ret = OSAMsgQRecv(ad->cmdq, (UINT8 *)&msg, sizeof(tcp_msg_t), ad->task_timeout);
        if (ret == OS_SUCCESS) {
            switch (msg.msgId) {
            case TCP_CMD_START:
                tcp_cmd_start(ad);
                break;
            case TCP_CMD_STOP:
                tcp_cmd_stop(ad, msg.extra);
                break;
            case TCP_CMD_SUSPEND:
                tcp_cmd_suspend(ad, msg.extra);
                break;
            case TCP_CMD_WAKEUP:
                tcp_cmd_wakeup(ad, msg.extra);
                break;
            default:
                break;
            }
        // 当(ad->task_timeout == 心跳周期)，用于触发心跳
        } else if (ret == OS_TIMEOUT) {
            tcp_timeout(ad);
        } else {
            ASSERT(0);
        }
    }
}

void lqtcp_main(void)
{
    int ret;
    tcp_data_t *ad;

    LOG_PRINTF("%s: +\n", __func__);

    ad = tcp_ad = malloc(sizeof(*tcp_ad));
    ASSERT(tcp_ad);
    memset(ad, 0x00, sizeof(*ad));

    ad->task_timeout = OSA_SUSPEND;
    ad->fd_sock = -1;

    ret = OSAMutexCreate(&ad->mutex_ref, OS_PRIORITY);
    ASSERT(ret == OS_SUCCESS);

    ret = OSAFlagCreate(&ad->flag_net_ref);
    ASSERT(ret == OS_SUCCESS);
    ad->task_net_stack = malloc((1024*3));
    ASSERT(ad->task_net_stack);
    ret = OSATaskCreate(&ad->task_net_ref, ad->task_net_stack, (1024*3), 88, "tcp_net_task", tcp_net_task, ad);
    ASSERT(ret == OS_SUCCESS);

    ret = OSAMsgQCreate(&ad->cmdq, "linkmsg", sizeof(tcp_msg_t), 30, OS_FIFO);
    ASSERT(ret == OS_SUCCESS);
    ad->task_stack = malloc((1024*5));
    ASSERT(ad->task_stack);
    ret = OSATaskCreate(&ad->task_ref, ad->task_stack, (1024*5), 86, "tcp_task", tcp_task, ad);
    ASSERT(ret == OS_SUCCESS);
}


